/*******************************************************************************
 * Copyright (c) 2010 marvin.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 *
 * Contributors:
 *     marvin - initial API and implementation
 ******************************************************************************/

package com.marv42.ebt.newnote;

import java.io.IOException;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;

import android.content.Context;
//import android.content.pm.PackageManager;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;



public class LocationTask extends AsyncTask<Void, String, Location>
{
   private static final long MAX_WAIT_TIME_MS = 30*1000;
   private static final long SLEEP_TIME_MS    = 5000;

   private static final int NUMBER_ADDRESSES = 5;

   private LocationManager mLocationManager;
   private LocationResult  mLocationResult;

   private Location mGpsLocation;
   private Location mNetworkLocation;

   private MyLocationListener mGpsLocationListener;
   private MyLocationListener mNetworkLocationListener;

   private final Context mContext;



   public static abstract class LocationResult
   {
       public abstract void
       gotLocation(Location location, String provider);
   }



   public LocationTask(final Context context)
   {
      mContext = context;
   }



   @Override
   protected void
   onPreExecute()
   {
      mLocationManager = (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);

      mLocationResult = new LocationResult()
      {
         @Override
         public void
         gotLocation(final Location l, final String provider)
         {
            if (provider == LocationManager.GPS_PROVIDER    )
               mGpsLocation     = l;
            if (provider == LocationManager.NETWORK_PROVIDER)
               mNetworkLocation = l;
         }
      };

      if (isGpsEnabled())
      {
         //Log.d(EbtNewNote.LOG_TARGET, "GPS enabled");
         mGpsLocationListener = new MyLocationListener(LocationManager.GPS_PROVIDER);
         mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
                                                 0, 0, mGpsLocationListener);
      }

      if (isNetworkEnabled())
      {
         //Log.d(EbtNewNote.LOG_TARGET, "network location enabled");
         mNetworkLocationListener = new MyLocationListener(LocationManager.NETWORK_PROVIDER);
         mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
                                                 0, 0, mNetworkLocationListener);
      }
   }



   @Override
   protected Location
   doInBackground(Void... params)
   {
      return getLocation();
   }



   protected void
   onProgressUpdate(String... text)
   {
      Toast.makeText(mContext, text[0], Toast.LENGTH_LONG).show();
   }



   @Override
   protected void
   onPostExecute(final Location l)
   {
      if (mGpsLocationListener     != null)
         mLocationManager.removeUpdates(mGpsLocationListener);
      if (mNetworkLocationListener != null)
         mLocationManager.removeUpdates(mNetworkLocationListener);
      //Log.d(EbtNewNote.LOG_TARGET, "[LocationTask] stopped all location updates");

      setLocationValues(l);

      if (! PreferenceManager.getDefaultSharedPreferences(mContext).edit()
            .putBoolean(mContext.getString(R.string.pref_getting_location_key), false)
            .commit())
         Log.e(EbtNewNote.LOG_TARGET, "Editor's commit failed");
   }



   private boolean
   isGpsEnabled()
   {
      boolean is = false;
//      if (! mContext.getApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS))
//         return false;
      try
      {
         is = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
      }
      catch (IllegalArgumentException e)
      {
         Log.d(EbtNewNote.LOG_TARGET, ": IllegalArgumentException GPS_PROVIDER: " + e.getMessage());
      }

      return is;
   }



   private boolean
   isNetworkEnabled()
   {
      return mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
   }



   private Location
   getLocation()
   {
      Long t = Calendar.getInstance().getTimeInMillis();

      while (Calendar.getInstance().getTimeInMillis() - t < MAX_WAIT_TIME_MS &&
             mGpsLocation == null &&
             (mNetworkLocation == null || isGpsEnabled()) &&
             (isNetworkEnabled() || isGpsEnabled()))
         SystemClock.sleep(SLEEP_TIME_MS);

      if (mGpsLocation != null)
      {
         publishProgress(mContext.getString(R.string.location_accurate));
         //Log.d(EbtNewNote.LOG_TARGET, "got a GPS location");
         return mGpsLocation;
      }

      if (mNetworkLocation != null)
      {
         publishProgress(mContext.getString(R.string.location_no_accurate));
         //Log.d(EbtNewNote.LOG_TARGET, "got a network location");
         return mNetworkLocation;
      }

      publishProgress(mContext.getString(R.string.location_no_up_to_date));
      //Log.d(EbtNewNote.LOG_TARGET, "no up-to-date location");

      mGpsLocation     = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER    );
      mNetworkLocation = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);

      if (mGpsLocation != null && mNetworkLocation == null)
         return mGpsLocation;
      if (mGpsLocation == null && mNetworkLocation != null)
         return mNetworkLocation;
      if (mGpsLocation != null && mNetworkLocation != null &&
          mGpsLocation.getTime() > mNetworkLocation.getTime())
         return mGpsLocation;
      else
         return mNetworkLocation;
   }



   private void
   setLocationValues(Location l)
   {
      if (l == null)
      {
         //Log.d(LOG_TARGET, "location: null");
         Toast.makeText(mContext, mContext.getString(R.string.no_location), Toast.LENGTH_LONG).show();
         return;
      }

      //Log.d(EbtNewNote.LOG_TARGET, "location: " + l.getLatitude() + ", " + l.getLongitude());
      try
      {
         final Geocoder gc = new Geocoder(mContext, Locale.ENGLISH);
         List<Address> addresses = gc.getFromLocation(
            l.getLatitude(), l.getLongitude(), NUMBER_ADDRESSES);
         //Log.d(EbtNewNote.LOG_TARGET, "Geocoder got " + addresses.size() + " address(es)");

         if (addresses.size() == 0)
            Toast.makeText(mContext, mContext.getString(R.string.no_address) + ": " + l.getLatitude() + ", " + l.getLongitude() + ".", Toast.LENGTH_LONG).show();

         for (Address a : addresses)
         {
            if (a == null)
               continue;

            ((EbtNewNote) mContext).setLocationValues(
               new LocationValues(a.getCountryName(), a.getLocality(), a.getPostalCode(), true));
         }
      }
      catch (IOException e)
      {
         Log.e(EbtNewNote.LOG_TARGET, "Geocoder IOException: " + e.getMessage());
         Toast.makeText(mContext, mContext.getString(R.string.geocoder_exception) + ": " + e.getMessage() + ".", Toast.LENGTH_LONG).show();
      }
   }



   private final class MyLocationListener implements LocationListener
   {
      private String mProvider;



      public MyLocationListener(String provider)
      {
         mProvider = provider;
      }



      public void
      onLocationChanged(Location l)
      {
         //Log.d(EbtNewNote.LOG_TARGET, "location changed");
         mLocationResult.gotLocation(l, mProvider);
      }



      public void
      onStatusChanged(String provider, int status, Bundle extras) {}



      public void
      onProviderEnabled(String provider) {}



      public void
      onProviderDisabled(String provider) {}
   }
}
